/**
 * Copyright 2020 jianggujin (www.jianggujin.com).
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jianggujin.dbfly.mybatis.entity;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import com.jianggujin.dbfly.mybatis.JDBFlyConfiguration;
import com.jianggujin.dbfly.mybatis.annotation.JNameStyle;
import com.jianggujin.dbfly.mybatis.annotation.JOverwriteGlobal;
import com.jianggujin.dbfly.mybatis.annotation.JTransient;
import com.jianggujin.dbfly.mybatis.column.JColumn;
import com.jianggujin.dbfly.mybatis.column.JExcludeInsert;
import com.jianggujin.dbfly.mybatis.column.JExcludeSelect;
import com.jianggujin.dbfly.mybatis.column.JExcludeUpdate;
import com.jianggujin.dbfly.mybatis.column.JJdbcType;
import com.jianggujin.dbfly.mybatis.column.JOrder;
import com.jianggujin.dbfly.mybatis.column.JTypeHandler;
import com.jianggujin.dbfly.mybatis.column.JUseJavaType;
import com.jianggujin.dbfly.mybatis.column.JUseNotEmpty;
import com.jianggujin.dbfly.mybatis.constant.JIdGeneratorStrategy;
import com.jianggujin.dbfly.mybatis.constant.JKeyOrder;
import com.jianggujin.dbfly.mybatis.dialect.JIdentity;
import com.jianggujin.dbfly.mybatis.id.JId;
import com.jianggujin.dbfly.mybatis.id.JSqlGenerator;
import com.jianggujin.dbfly.mybatis.id.JUseGeneratedKeys;
import com.jianggujin.dbfly.mybatis.id.JUseIdGenerator;
import com.jianggujin.dbfly.mybatis.id.JUseIdentityDialect;
import com.jianggujin.dbfly.mybatis.id.JUseSql;
import com.jianggujin.dbfly.mybatis.id.JUseSqlGenerator;
import com.jianggujin.dbfly.mybatis.table.JTable;
import com.jianggujin.dbfly.mybatis.table.JUseDynamicTableName;
import com.jianggujin.dbfly.mybatis.value.JGeneratedValue;
import com.jianggujin.dbfly.mybatis.version.JVersion;
import com.jianggujin.dbfly.util.JBeanUtils;
import com.jianggujin.dbfly.util.JDBFlyException;
import com.jianggujin.dbfly.util.JStringUtils;
import com.jianggujin.dbfly.util.JStyle;

/**
 * 实体解析器
 * 
 * @author jianggujin
 *
 */
public class JEntityResolver {
    private final JDBFlyConfiguration dbFlyConfiguration;

    private static final Map<Class<?>, JEntity> CACHE = new ConcurrentHashMap<Class<?>, JEntity>();

    public JEntityResolver(JDBFlyConfiguration dbFlyConfiguration) {
        this.dbFlyConfiguration = dbFlyConfiguration;
    }

    /**
     * 解析实体类
     * 
     * @param entityClass
     * @return
     */
    public JEntity resolveEntity(Class<?> entityClass) {
        JEntity entity = CACHE.get(entityClass);
        if (entity != null) {
            return entity;
        }
        JStyle style = dbFlyConfiguration.getStyle();
        // style，该注解优先于全局配置
        if (entityClass.isAnnotationPresent(JNameStyle.class)) {
            JNameStyle nameStyle = entityClass.getAnnotation(JNameStyle.class);
            style = nameStyle.value();
        }
        entity = this.resolveEntityBasic(entityClass, style);
        this.resolveEntityColumn(entity, style);
        this.resolveOrderByClause(entity);
        CACHE.put(entityClass, entity);
        return entity;
    }

    /**
     * 解析实体基本信息
     * 
     * @param entityClass
     * @param style
     * @return
     */
    private JEntity resolveEntityBasic(Class<?> entityClass, JStyle style) {
        JEntity entity = new JEntity(entityClass);
        String tableName = null;
        if (entityClass.isAnnotationPresent(JTable.class)) {
            JTable table = entityClass.getAnnotation(JTable.class);
            if (JStringUtils.isNotEmpty(table.value())) {
                tableName = table.value();
            }
            if (JStringUtils.isNotEmpty(table.schema())) {
                entity.setSchema(table.schema());
            }
        } else if (entityClass.isAnnotationPresent(JUseDynamicTableName.class)) {
            JUseDynamicTableName dynamicTableName = entityClass.getAnnotation(JUseDynamicTableName.class);
            entity.setTableNameGeneratorClass(dynamicTableName.value());
            if (JStringUtils.isNotEmpty(dynamicTableName.name())) {
                tableName = dynamicTableName.name();
            }
            if (JStringUtils.isNotEmpty(dynamicTableName.schema())) {
                entity.setSchema(dynamicTableName.schema());
            }
        }
        if (JStringUtils.isEmpty(tableName)) {
            tableName = JStringUtils.convertByStyle(entityClass.getSimpleName(), style);
        }
        entity.setTableName(tableName);
        if (JStringUtils.isEmpty(entity.getSchema()) && JStringUtils.isNotEmpty(this.dbFlyConfiguration.getSchema())) {
            entity.setSchema(this.dbFlyConfiguration.getSchema());
        }
        return entity;
    }

    /**
     * 解析实体列相关属性
     * 
     * @param entity
     * @param style
     */
    private void resolveEntityColumn(JEntity entity, JStyle style) {
        Class<?> entityClass = entity.getEntityClass();
        // 获取所有属性
        List<Field> fields = JBeanUtils.findFields(entityClass, null);

        // 解析所有列
        List<JEntityColumn> columns = new ArrayList<>();
        for (Field field : fields) {
            Optional<JEntityColumn> optional = resolveEntityColumn(field, style, entity);
            if (optional.isPresent()) {
                columns.add(optional.get());
            }
        }
        Collections.sort(columns);
        // 设置所有列
        entity.setColumns(Collections.unmodifiableList(columns));
        List<JEntityIdColumn> idColumns = columns.stream().filter(JEntityColumn::isId)
                .map(item -> (JEntityIdColumn) item).collect(Collectors.toList());
        // 设置主键列
        entity.setIdColumns(Collections.unmodifiableList(idColumns));
        // 校验主键是否合法，仅有JAVA代码生成或@JId形式的主键允许多个
        if (entity.hasId()) {
            long count = idColumns.stream().filter(item -> item.getIdGeneratorStrategy() != JIdGeneratorStrategy.JAVA
                    && item.getIdGeneratorStrategy() != JIdGeneratorStrategy.NONE).count();
            if (count > 1) {
                throw new JDBFlyException("{}类中主键不符合要求", entityClass.getCanonicalName());
            }
        }
        if (columns.stream().filter(JEntityColumn::isVersion).count() > 1) {
            throw new JDBFlyException("{}类中@JVersion不符合要求", entityClass.getCanonicalName());
        }

        // 设置属性映射
        entity.setPropertyMap(Collections
                .unmodifiableMap(columns.stream().collect(Collectors.toMap(JEntityColumn::getProperty, item -> item))));
    }

    /**
     * 处理字段
     * 
     * @param field
     * @param style
     * @param entity
     * @return
     */
    private Optional<JEntityColumn> resolveEntityColumn(Field field, JStyle style, JEntity entity) {
        // 排除字段
        if (isTransient(field)) {
            return Optional.empty();
        }
        style = field.isAnnotationPresent(JNameStyle.class) ? field.getAnnotation(JNameStyle.class).value() : style;
        Class<? extends Annotation>[] overwrites = getOverwrites(field, entity.getEntityClass());
        JEntityColumn column = this.resolveEntityColumnBasic(field, entity, overwrites, style);
        this.resolveEntityColumnSUIable(column, overwrites);

        if (column.isId()) {
            this.resolveIdGenerator((JEntityIdColumn) column);
        } else {
            if (field.isAnnotationPresent(JVersion.class)) {
                column.setVersionGeneratorClass(field.getAnnotation(JVersion.class).value());
            }
            if (!column.isVersion()) {
                if (field.isAnnotationPresent(JGeneratedValue.class)) {
                    column.setValueGeneratorClass(field.getAnnotation(JGeneratedValue.class).value());
                } else if (this.contains(this.dbFlyConfiguration.getExcludeUpdates(), column.getProperty())) {
                    column.setValueGeneratorClass(this.dbFlyConfiguration.getValueGeneratorClass());
                }
            }
        }
        return Optional.of(column);
    }

    /**
     * 判断是否需要排除
     * 
     * @param field
     * @return
     */
    private boolean isTransient(Field field) {
        if (field.isAnnotationPresent(JTransient.class) || Modifier.isTransient(field.getModifiers())) {
            return true;
        }
        return false;
    }

    /**
     * 判断是否为主键
     * 
     * @param field
     * @return
     */
    @SuppressWarnings("unchecked")
    private boolean isId(Field field) {
        // 是否主键
        Class<?>[] anns = { JUseGeneratedKeys.class, JUseIdentityDialect.class, JUseSql.class, JUseSqlGenerator.class,
                JUseIdGenerator.class, JId.class };
        boolean isId = Arrays.stream(anns)
                .anyMatch(item -> field.isAnnotationPresent((Class<? extends Annotation>) item));
        return isId;
    }

    /**
     * 获得需要覆盖的注解
     * 
     * @param field
     * @param entityClass
     * @return
     */
    private Class<? extends Annotation>[] getOverwrites(Field field, Class<?> entityClass) {
        JOverwriteGlobal overwriteGlobal = field.isAnnotationPresent(JOverwriteGlobal.class)
                ? field.getAnnotation(JOverwriteGlobal.class)
                : (entityClass.isAnnotationPresent(JOverwriteGlobal.class)
                        ? entityClass.getAnnotation(JOverwriteGlobal.class)
                        : null);
        return overwriteGlobal == null ? null : overwriteGlobal.value();
    }

    /**
     * 解析属性基本信息
     * 
     * @param field
     * @param entity
     * @param overwrites
     * @param style
     * @return
     */
    private JEntityColumn resolveEntityColumnBasic(Field field, JEntity entity,
            Class<? extends Annotation>[] overwrites, JStyle style) {
        // 是否主键
        boolean isId = isId(field);
        JEntityColumn column = isId ? new JEntityIdColumn(entity, field) : new JEntityColumn(entity, field);
        // 列名
        String columnName = null;
        if (field.isAnnotationPresent(JColumn.class)) {
            JColumn col = field.getAnnotation(JColumn.class);
            if (JStringUtils.isNotEmpty(col.value())) {
                columnName = col.value();
            }
        }
        if (JStringUtils.isEmpty(columnName)) {
            columnName = JStringUtils.convertByStyle(field.getName(), style);
        }
        column.setColumn(columnName);

        // jdbc类型
        column.setJdbcType(
                field.isAnnotationPresent(JJdbcType.class) ? field.getAnnotation(JJdbcType.class).value() : null);
        // 类型处理器
        column.setTypeHandler(
                field.isAnnotationPresent(JTypeHandler.class) ? field.getAnnotation(JTypeHandler.class).value() : null);
        // 是否使用 {xx, javaType=xxx}
        if (field.isAnnotationPresent(JUseJavaType.class)) {
            column.setUseJavaType(true);
        } else if (this.overwriteGlobal(overwrites, JUseJavaType.class)) {
            column.setUseJavaType(this.dbFlyConfiguration.isUseJavaType());
        }
        // 是否判断字符串是否为""
        if (String.class.equals(field.getType())) {
            if (field.isAnnotationPresent(JUseNotEmpty.class)) {
                column.setNotEmpty(true);
            } else if (this.overwriteGlobal(overwrites, JUseNotEmpty.class)) {
                column.setNotEmpty(this.dbFlyConfiguration.isNotEmpty());
            }
        }
        // 是否参与排序
        column.setOrder(field.isAnnotationPresent(JOrder.class));
        return column;
    }

    /**
     * 解析属性是否支持查询、修改、插入
     * 
     * @param column
     * @param overwrites
     */
    private void resolveEntityColumnSUIable(JEntityColumn column, Class<? extends Annotation>[] overwrites) {
        Field field = column.getField();
        String property = column.getProperty();
        // 是否可查询
        if (field.isAnnotationPresent(JExcludeSelect.class)) {
            column.setSelectable(false);
        } else if (this.overwriteGlobal(overwrites, JExcludeSelect.class)) {
            column.setSelectable(!this.contains(this.dbFlyConfiguration.getExcludeSelects(), property));
        }
        // 是否可修改
        if (field.isAnnotationPresent(JExcludeUpdate.class)) {
            column.setUpdatable(false);
        } else if (this.overwriteGlobal(overwrites, JExcludeUpdate.class)) {
            column.setUpdatable(!this.contains(this.dbFlyConfiguration.getExcludeUpdates(), property));
        }
        // 是否可插入
        if (field.isAnnotationPresent(JExcludeInsert.class)) {
            column.setInsertable(false);
        } else if (this.overwriteGlobal(overwrites, JExcludeInsert.class)) {
            column.setInsertable(!this.contains(this.dbFlyConfiguration.getExcludeInserts(), property));
        }

        // 自增主键时，不需要插入
        if (column.isId() && field.isAnnotationPresent(JUseGeneratedKeys.class)) {
            column.setInsertable(false);
        }
    }

    /**
     * 处理主键生成策略
     * 
     * @param idColumn
     */
    private void resolveIdGenerator(JEntityIdColumn idColumn) {
        Field field = idColumn.getField();
        if (field.isAnnotationPresent(JUseGeneratedKeys.class)) {// JDBC自动生成
            idColumn.setIdGeneratorStrategy(JIdGeneratorStrategy.JDBC);
        } else if (field.isAnnotationPresent(JUseIdentityDialect.class)) {// 方言查询
            if (this.dbFlyConfiguration.getDialect() == null) {
                throw new JDBFlyException("方言不存在");
            }
            JIdentity identity = this.dbFlyConfiguration.getDialect().getIdentity();
            if (identity == null) {
                throw new JDBFlyException("方言不支持查询或生成主键");
            }
            idColumn.setIdGeneratorStrategy(JIdGeneratorStrategy.IDENTITY);
            idColumn.setKeyOrder(identity.getKeyOrder());
            idColumn.setIdGeneratorSql(identity.getRetrievalStatement());
        } else if (field.isAnnotationPresent(JUseSql.class)) {// 自定义SQL
            idColumn.setIdGeneratorStrategy(JIdGeneratorStrategy.IDENTITY);
            JUseSql useSql = field.getAnnotation(JUseSql.class);
            idColumn.setKeyOrder(useSql.order());
            idColumn.setIdGeneratorSql(useSql.value());
        } else if (field.isAnnotationPresent(JUseSqlGenerator.class)) {// 动态自定义SQL
            idColumn.setIdGeneratorStrategy(JIdGeneratorStrategy.IDENTITY);
            JUseSqlGenerator useGeneratorSql = field.getAnnotation(JUseSqlGenerator.class);
            idColumn.setKeyOrder(useGeneratorSql.order());
            try {
                JSqlGenerator genSql = useGeneratorSql.value().newInstance();
                idColumn.setIdGeneratorSql(genSql.generateSql(idColumn.getEntity(), idColumn));
            } catch (Exception e) {
                throw new JDBFlyException("实例化JSqlGenerator失败:", e);
            }
        } else if (field.isAnnotationPresent(JUseIdGenerator.class)) {// JAVA代码
            idColumn.setIdGeneratorStrategy(JIdGeneratorStrategy.JAVA);
            JUseIdGenerator useIdGenerator = field.getAnnotation(JUseIdGenerator.class);
            idColumn.setIdGeneratorClass(useIdGenerator.value());
            idColumn.setKeyOrder(JKeyOrder.AFTER);
        }
        if (idColumn.getIdGeneratorStrategy() == null) {
            idColumn.setIdGeneratorStrategy(JIdGeneratorStrategy.NONE);
        }
    }

    /**
     * 获取默认的orderby语句
     *
     * @param entity
     * @return
     */
    private void resolveOrderByClause(JEntity entity) {
        StringBuilder orderBy = new StringBuilder();
        JEntityColumn[] columns = entity.getColumns().stream().filter(JEntityColumn::isOrder)
                .sorted((c1, c2) -> c1.getField().getAnnotation(JOrder.class).priority()
                        - c2.getField().getAnnotation(JOrder.class).priority())
                .toArray(JEntityColumn[]::new);
        boolean first = true;
        for (JEntityColumn column : columns) {
            if (!first) {
                orderBy.append(", ");
            }
            first = false;
            orderBy.append(column.getColumn()).append(" ")
                    .append(column.getField().getAnnotation(JOrder.class).value());
        }
        entity.setOrderByClause(orderBy.toString());
    }

    /**
     * 获得实体类
     * 
     * @param clazz
     * @return
     */
    public static JEntity getEntity(Class<?> clazz) {
        return CACHE.get(clazz);
    }

    /**
     * 判断数组中是否包含指定元素
     * 
     * @param array
     * @param value
     * @return
     */
    private boolean contains(String[] array, String value) {
        return array != null && Arrays.stream(array).anyMatch(item -> JStringUtils.equals(item, value));
    }

    /**
     * 是否重写全局配置
     * 
     * @param array
     * @param value
     * @return
     */
    private boolean overwriteGlobal(Class<? extends Annotation>[] array, Class<? extends Annotation> value) {
        return array != null && (array.length == 0 || Arrays.stream(array).anyMatch(item -> item.equals(value)));
    }
}
